package com.hongji.lms5xxcommunicator.opc.trigger;

import com.hongji.lms5xxcommunicator.entity.ScanPointEntity;
import com.hongji.lms5xxcommunicator.grid.region.Point;
import com.hongji.lms5xxcommunicator.grid.region.RegionRect;
import com.hongji.lms5xxcommunicator.mapper.ScanPointDataMapper;
import com.hongji.lms5xxcommunicator.opc.util.OpcIoHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.core.env.Environment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
public class TriggerExtremumService {

    private final OpcIoHelper io;                 // 读/写PLC（外部同一个实例传进来）
    private final ScanPointDataMapper mapper;     // 按区域查 DB

    // 节点与结果
    private final String regionXMinNode;
    private final String regionXMaxNode;
    private final String regionYMinNode;
    private final String regionYMaxNode;
    private final String resultModeNode;
    private final String resultXNode;
    private final String resultYNode;
    private final String resultZNode;

    private final double regionUnitScale; // PLC→计算
    private final double dbUnitScale;     // DB→计算
    private final int    maxPoints;

    // 排除矩形（计算单位）
    private final List<RegionRect> excludeRectsCalc;

    private final Object computeLock = new Object();

    /** 外部入口：code=0/1/2（0=空闲，1=求最高，2=求最低） */
    public void onTrigger(int code) {
        if (code != 1 && code != 2) {
            log.info("触发码={}（空闲），本次不计算。", code);
            return;
        }
        try {
            computePointExtremumFlow(code);
        } catch (Exception e) {
            log.error("模块A触发计算失败：{}", e.getMessage(), e);
        }
    }

    /** 整体流程：读 PLC 区域 → DB 取点（含排除） → 算极值 → 写回 PLC */
    private void computePointExtremumFlow(int code) {
        synchronized (computeLock) {
            RegionRect region = readRegionFromPlc();
            if (region == null) {
                log.warn("读取 PLC 区域失败，取消本次计算。");
                pushEmptyThenMaybeReset(code);
                return;
            }
            List<ScanPointEntity> rows = fetchPointsByRegion(region, maxPoints);
            if (rows == null || rows.isEmpty()) {
                log.info("区域无点数据，写回空结果：{}", region);
                pushEmptyThenMaybeReset(code);
                return;
            }
            Point best = pickExtremum(rows, code);
            if (best == null) {
                log.info("区域无有效点（z 无效或全部被排除），写回空结果。");
                pushEmptyThenMaybeReset(code);
                return;
            }
            pushPointThenMaybeReset(code, best);
            log.info("区域A最高点/最低点计算完成：mode={}，x={}，y={}，z={}，id={}",
                    code == 1 ? "MAX" : "MIN", best.getX(), best.getY(), best.getZ(), best.getId());
        }
    }

    private RegionRect readRegionFromPlc() {
        try {
            double xminPlc = io.readDoubleFlexible(regionXMinNode);
            double xmaxPlc = io.readDoubleFlexible(regionXMaxNode);
            double yminPlc = io.readDoubleFlexible(regionYMinNode);
            double ymaxPlc = io.readDoubleFlexible(regionYMaxNode);

            // 统一坐标：只做单位换算
            double xmin = xminPlc * regionUnitScale;
            double xmax = xmaxPlc * regionUnitScale;
            double ymin = yminPlc * regionUnitScale;
            double ymax = ymaxPlc * regionUnitScale;

            if (Double.isNaN(xmin) || Double.isNaN(xmax) || Double.isNaN(ymin) || Double.isNaN(ymax)) return null;
            if (xmax < xmin) { double t=xmin; xmin=xmax; xmax=t; }
            if (ymax < ymin) { double t=ymin; ymin=ymax; ymax=t; }

            log.info("PLC 区域（计算单位，统一坐标）：x[{}, {}], y[{}, {}] (unitScale={})",
                    xmin, xmax, ymin, ymax, regionUnitScale);
            return new RegionRect(xmin, xmax, ymin, ymax);
        } catch (Exception e) {
            log.error("从PLC读取区域失败：{}", e.getMessage(), e);
            return null;
        }
    }

    /** DB 读取：把排除矩形下推到 SQL */
    private List<ScanPointEntity> fetchPointsByRegion(RegionRect r, int limit) {
        try {
            double xMinDb = r.getXMin() / dbUnitScale;
            double xMaxDb = r.getXMax() / dbUnitScale;
            double yMinDb = r.getYMin() / dbUnitScale;
            double yMaxDb = r.getYMax() / dbUnitScale;

            List<RegionRect> excludesDb = toDbUnitExcludes(excludeRectsCalc, dbUnitScale);

            List<ScanPointEntity> list;
            if (excludesDb != null && !excludesDb.isEmpty()) {
                list = mapper.selectByRegionWithExcludes(xMinDb, xMaxDb, yMinDb, yMaxDb, excludesDb, limit);
                log.info("DB 返回点数(含排除)：{}（limit={}，dbUnitScale={}，excludeN={}）",
                        (list == null ? 0 : list.size()), limit, dbUnitScale, excludesDb.size());
            } else {
                list = mapper.selectByRegion(xMinDb, xMaxDb, yMinDb, yMaxDb, limit);
                log.info("DB 返回点数(无排除)：{}（limit={}，dbUnitScale={}）",
                        (list == null ? 0 : list.size()), limit, dbUnitScale);
            }
            return list;
        } catch (Exception e) {
            log.error("DB 查询失败：{}", e.getMessage(), e);
            return Collections.emptyList();
        }
    }

    /** 计算单位 -> DB 单位，并做一次 min/max 归一（避免 xMax<xMin 等） */
    private List<RegionRect> toDbUnitExcludes(List<RegionRect> calcRects, double dbUnitScale) {
        if (calcRects == null || calcRects.isEmpty()) return Collections.emptyList();
        ArrayList<RegionRect> out = new ArrayList<>(calcRects.size());
        for (RegionRect e : calcRects) {
            if (e == null) continue;
            double exmin = e.getXMin() / dbUnitScale;
            double exmax = e.getXMax() / dbUnitScale;
            double eymin = e.getYMin() / dbUnitScale;
            double eymax = e.getYMax() / dbUnitScale;
            if (exmax < exmin) { double t = exmin; exmin = exmax; exmax = t; }
            if (eymax < eymin) { double t = eymin; eymin = eymax; eymax = t; }
            out.add(new RegionRect(exmin, exmax, eymin, eymax));
        }
        return out;
    }

    private Point pickExtremum(List<ScanPointEntity> rows, int code) {
        boolean pickMax = (code == 1);
        Point best = null;
        for (ScanPointEntity r : rows) {
            if (r == null || r.getZ()==null || r.getX()==null || r.getY()==null) continue;
            double z = r.getZ() * dbUnitScale;
            if (Double.isNaN(z) || Double.isInfinite(z)) continue;
            double x = r.getX() * dbUnitScale;
            double y = r.getY() * dbUnitScale;
            Long id = (r.getId() == null ? null : r.getId());

            if (best == null) best = new Point(x,y,z,id);
            else {
                if (pickMax && z > best.getZ()) best = new Point(x,y,z,id);
                if (!pickMax && z < best.getZ()) best = new Point(x,y,z,id);
            }
        }
        return best;
    }

    private void pushEmptyThenMaybeReset(int code) {
        io.writeIfPresent(resultModeNode, code);
        io.writeIfPresent(resultXNode, Double.NaN);
        io.writeIfPresent(resultYNode, Double.NaN);
        io.writeIfPresent(resultZNode, Double.NaN);
    }

    /** 统一坐标：结果直接按单位换算写回 PLC */
    private void pushPointThenMaybeReset(int code, Point p) {
        double plcX = p.getX() / regionUnitScale;
        double plcY = p.getY() / regionUnitScale;
        double plcZ = p.getZ() / regionUnitScale;
        io.writeIfPresent(resultModeNode, code);
        io.writeIfPresent(resultXNode, plcX);
        io.writeIfPresent(resultYNode, plcY);
        io.writeIfPresent(resultZNode, plcZ);
    }

    // --------------------------------------------------------------------
    // 静态工厂：从 Spring Environment 读取配置，但使用你传入的 *同一个* OpcIoHelper
    // --------------------------------------------------------------------
    public static TriggerExtremumService fromEnv(Environment env,
                                                 OpcIoHelper io,
                                                 ScanPointDataMapper mapper) {
        Binder b = Binder.get(env);

        String xMinNode = b.bind("opc.trigger.region.xMin", Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.region.xMin"));
        String xMaxNode = b.bind("opc.trigger.region.xMax", Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.region.xMax"));
        String yMinNode = b.bind("opc.trigger.region.yMin", Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.region.yMin"));
        String yMaxNode = b.bind("opc.trigger.region.yMax", Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.region.yMax"));

        String modeNode = b.bind("opc.trigger.result.mode_node_id", Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.result.mode_node_id"));
        String rxNode   = b.bind("opc.trigger.result.x_node_id",    Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.result.x_node_id"));
        String ryNode   = b.bind("opc.trigger.result.y_node_id",    Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.result.y_node_id"));
        String rzNode   = b.bind("opc.trigger.result.z_node_id",    Bindable.of(String.class))
                .orElseThrow(() -> new IllegalStateException("Missing: opc.trigger.result.z_node_id"));

        double regionUnitScale = b.bind("opc.trigger.region.unit_scale", Bindable.of(Double.class)).orElse(1.0);
        double dbUnitScale     = b.bind("opc.trigger.db.unit_scale",     Bindable.of(Double.class)).orElse(1.0);
        int    maxPoints       = b.bind("opc.trigger.max_points",        Bindable.of(Integer.class)).orElse(200000);

        boolean autoResetModeToZero = b.bind("opc.trigger.auto_reset_mode_to_zero", Bindable.of(Boolean.class)).orElse(true);
        int     resetDelayMs        = b.bind("opc.trigger.reset_delay_ms",          Bindable.of(Integer.class)).orElse(50);

        // 读取排除矩形（计算单位）
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> rectMaps = (List<Map<String, Object>>) (List<?>)
                b.bind("opc.trigger.exclude.rects", Bindable.listOf(Map.class))
                        .orElse(Collections.emptyList());

        List<RegionRect> excludesCalc = new ArrayList<>();
        for (Map<String, Object> m : rectMaps) {
            if (m == null) continue;
            Double xMin = toD(m.get("xMin"));
            Double xMax = toD(m.get("xMax"));
            Double yMin = toD(m.get("yMin"));
            Double yMax = toD(m.get("yMax"));
            if (xMin == null || xMax == null || yMin == null || yMax == null) continue;
            double a = xMin, b1 = xMax, c = yMin, d = yMax;
            if (b1 < a) { double t=a; a=b1; b1=t; }
            if (d  < c) { double t=c; c=d;  d=t; }
            excludesCalc.add(new RegionRect(a, b1, c, d));
        }

        return new TriggerExtremumService(
                io, mapper,
                xMinNode, xMaxNode, yMinNode, yMaxNode,
                modeNode, rxNode, ryNode, rzNode,
                regionUnitScale, dbUnitScale, maxPoints,
                excludesCalc
        );
    }

    private static Double toD(Object v) {
        if (v == null) return null;
        if (v instanceof Number) return ((Number) v).doubleValue();
        try { return Double.parseDouble(String.valueOf(v)); } catch (Exception e) { return null; }
    }
}
